/**
* Copyright (c) 2009 Juwi MacMillan Group GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.juwimm.cms.authorization.jaas.ldap;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Principal;
import java.security.acl.Group;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;
import javax.transaction.Transaction;
import org.apache.log4j.Logger;
import org.jboss.security.SimpleGroup;
import org.jboss.tm.TransactionDemarcationSupport;
/**
* Common login module utility methods
*
* @author Scott.Stark@jboss.org
* @version $Revision: 57430 $
*/
public class Util {
/** Create the set of roles the user belongs to by parsing the roles.properties
data for username=role1,role2,... and username.XXX=role1,role2,...
patterns.
*
* @param targetUser - the username to obtain roles for
* @param roles - the Properties containing the user=roles mappings
* @param roleGroupSeperator - the character that seperates a username
* from a group name, e.g., targetUser[.GroupName]=roles
* @param aslm - the login module to use for Principal creation
* @return Group[] containing the sets of roles
*/
static Group[] getRoleSets(String targetUser, Properties roles, char roleGroupSeperator, AbstractServerLoginModule aslm) {
Logger log = aslm.log;
boolean trace = log.isTraceEnabled();
Enumeration users = roles.propertyNames();
SimpleGroup rolesGroup = new SimpleGroup("Roles");
ArrayList groups = new ArrayList();
groups.add(rolesGroup);
while (users.hasMoreElements() && targetUser != null) {
String user = (String) users.nextElement();
String value = roles.getProperty(user);
if (trace) log.trace("Checking user: " + user + ", roles string: " + value);
// See if this entry is of the form targetUser[.GroupName]=roles
// JBAS-3742 - skip potential '.' in targetUser
int index = user.indexOf(roleGroupSeperator, targetUser.length());
boolean isRoleGroup = false;
boolean userMatch = false;
if (index > 0 && targetUser.regionMatches(0, user, 0, index) == true)
isRoleGroup = true;
else
userMatch = targetUser.equals(user);
// Check for username.RoleGroup pattern
if (isRoleGroup == true) {
String groupName = user.substring(index + 1);
if (groupName.equals("Roles")) {
if (trace) log.trace("Adding to Roles: " + value);
parseGroupMembers(rolesGroup, value, aslm);
} else {
if (trace) log.trace("Adding to " + groupName + ": " + value);
SimpleGroup group = new SimpleGroup(groupName);
parseGroupMembers(group, value, aslm);
groups.add(group);
}
} else if (userMatch == true) {
if (trace) log.trace("Adding to Roles: " + value);
// Place these roles into the Default "Roles" group
parseGroupMembers(rolesGroup, value, aslm);
}
}
Group[] roleSets = new Group[groups.size()];
groups.toArray(roleSets);
return roleSets;
}
/** Execute the rolesQuery against the dsJndiName to obtain the roles for
the authenticated user.
@return Group[] containing the sets of roles
*/
static Group[] getRoleSets(String username, String dsJndiName, String rolesQuery, AbstractServerLoginModule aslm) throws LoginException {
return getRoleSets(username, dsJndiName, rolesQuery, aslm, false);
}
/** Execute the rolesQuery against the dsJndiName to obtain the roles for
the authenticated user.
@return Group[] containing the sets of roles
*/
static Group[] getRoleSets(String username, String dsJndiName, String rolesQuery, AbstractServerLoginModule aslm, boolean suspendResume) throws LoginException {
Logger log = aslm.log;
boolean trace = log.isTraceEnabled();
Connection conn = null;
HashMap setsMap = new HashMap();
PreparedStatement ps = null;
ResultSet rs = null;
Transaction tx = null;
if (suspendResume) {
tx = TransactionDemarcationSupport.suspendAnyTransaction();
if (trace) log.trace("suspendAnyTransaction");
}
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
conn = ds.getConnection();
// Get the user role names
if (trace) log.trace("Excuting query: " + rolesQuery + ", with username: " + username);
ps = conn.prepareStatement(rolesQuery);
try {
ps.setString(1, username);
} catch (ArrayIndexOutOfBoundsException ignore) {
// The query may not have any parameters so just try it
}
rs = ps.executeQuery();
if (rs.next() == false) {
if (trace) log.trace("No roles found");
if (aslm.getUnauthenticatedIdentity() == null) throw new FailedLoginException("No matching username found in Roles");
/* We are running with an unauthenticatedIdentity so create an
empty Roles set and return.
*/
Group[] roleSets = {new SimpleGroup("Roles")};
return roleSets;
}
do {
String name = rs.getString(1);
String groupName = rs.getString(2);
if (groupName == null || groupName.length() == 0) groupName = "Roles";
Group group = (Group) setsMap.get(groupName);
if (group == null) {
group = new SimpleGroup(groupName);
setsMap.put(groupName, group);
}
try {
Principal p = aslm.createIdentity(name);
if (trace) log.trace("Assign user to role " + name);
group.addMember(p);
} catch (Exception e) {
if (log.isDebugEnabled()) log.debug("Failed to create principal: " + name, e);
}
} while (rs.next());
} catch (NamingException ex) {
LoginException le = new LoginException("Error looking up DataSource from: " + dsJndiName);
le.initCause(ex);
throw le;
} catch (SQLException ex) {
LoginException le = new LoginException("Query failed");
le.initCause(ex);
throw le;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
} catch (Exception ex) {
}
}
if (suspendResume) {
TransactionDemarcationSupport.resumeAnyTransaction(tx);
if (trace) log.trace("resumeAnyTransaction");
}
}
Group[] roleSets = new Group[setsMap.size()];
setsMap.values().toArray(roleSets);
return roleSets;
}
/** Utility method which loads the given properties file and returns a
* Properties object containing the key,value pairs in that file.
* The properties files should be in the class path as this method looks
* to the thread context class loader (TCL) to locate the resource. If the
* TCL is a URLClassLoader the findResource(String) method is first tried.
* If this fails or the TCL is not a URLClassLoader getResource(String) is
* tried.
* @param defaultsName - the name of the default properties file resource
* that will be used as the default Properties to the ctor of the
* propertiesName Properties instance.
* @param propertiesName - the name of the properties file resource
* @param log - the logger used for trace level messages
* @return the loaded properties file if found
* @exception java.io.IOException thrown if the properties file cannot be found
* or loaded
*/
static Properties loadProperties(String defaultsName, String propertiesName, Logger log) throws IOException {
Properties bundle = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL defaultUrl = null;
URL url = null;
// First check for local visibility via a URLClassLoader.findResource
if (loader instanceof URLClassLoader) {
URLClassLoader ucl = (URLClassLoader) loader;
defaultUrl = ucl.findResource(defaultsName);
url = ucl.findResource(propertiesName);
log.trace("findResource: " + url);
}
// Do a general resource search
if (defaultUrl == null) defaultUrl = loader.getResource(defaultsName);
if (url == null) url = loader.getResource(propertiesName);
if (url == null && defaultUrl == null) {
String msg = "No properties file: " + propertiesName + " or defaults: " + defaultsName + " found";
throw new IOException(msg);
}
log.trace("Properties file=" + url + ", defaults=" + defaultUrl);
Properties defaults = new Properties();
if (defaultUrl != null) {
try {
InputStream is = defaultUrl.openStream();
defaults.load(is);
is.close();
if (log.isDebugEnabled()) log.debug("Loaded defaults, users=" + defaults.keySet());
} catch (Throwable e) {
if (log.isDebugEnabled()) log.debug("Failed to load defaults", e);
}
}
bundle = new Properties(defaults);
if (url != null) {
InputStream is = url.openStream();
if (is != null) {
bundle.load(is);
is.close();
} else {
throw new IOException("Properties file " + propertiesName + " not avilable");
}
if (log.isDebugEnabled()) log.debug("Loaded properties, users=" + bundle.keySet());
}
return bundle;
}
/** Utility method which loads the given properties file and returns a
* Properties object containing the key,value pairs in that file.
* The properties files should be in the class path as this method looks
* to the thread context class loader (TCL) to locate the resource. If the
* TCL is a URLClassLoader the findResource(String) method is first tried.
* If this fails or the TCL is not a URLClassLoader getResource(String) is
* tried. If not, an absolute path is tried.
* @param propertiesName - the name of the properties file resource
* @param log - the logger used for trace level messages
* @return the loaded properties file if found
* @exception java.io.IOException thrown if the properties file cannot be found
* or loaded
*/
static Properties loadProperties(String propertiesName, Logger log) throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = null;
// First check for local visibility via a URLClassLoader.findResource
if (loader instanceof URLClassLoader) {
URLClassLoader ucl = (URLClassLoader) loader;
url = ucl.findResource(propertiesName);
log.trace("findResource: " + url);
}
if (url == null) url = loader.getResource(propertiesName);
if (url == null) {
url = new URL(propertiesName);
}
log.trace("Properties file=" + url);
Properties bundle = new Properties();
if (url != null) {
InputStream is = url.openStream();
if (is != null) {
bundle.load(is);
is.close();
} else {
throw new IOException("Properties file " + propertiesName + " not avilable");
}
if (log.isDebugEnabled()) log.debug("Loaded properties, users=" + bundle.keySet());
}
return bundle;
}
/** Parse the comma delimited roles names given by value and add them to
* group. The type of Principal created for each name is determined by
* the createIdentity method.
*
* @see AbstractServerLoginModule#createIdentity(String)
*
* @param group - the Group to add the roles to.
* @param roles - the comma delimited role names.
*/
static void parseGroupMembers(Group group, String roles, AbstractServerLoginModule aslm) {
StringTokenizer tokenizer = new StringTokenizer(roles, ",");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
try {
Principal p = aslm.createIdentity(token);
group.addMember(p);
} catch (Exception e) {
aslm.log.warn("Failed to create principal for: " + token, e);
}
}
}
}